本篇我们与各位读者分享一些关于Loock Touch智能门锁的研究内容,该门锁由云丁科技公司出品,云丁科技是一家专注于研发和生产智能家居安全产品的公司,旗下有两大品牌,分别是针对家用市场的“鹿客”和针对公寓市场的“云丁”。Loock Touch是鹿客品牌的主打产品之一,其架构与云丁品牌的D2、D3智能门锁很相似,最新的鹿客旗舰产品是Loock Touch 2 Pro,其硬件架构与此前所有产品均不同,变化较大。
由于胖猴实验室一直和云丁科技有良好的合作关系,所以接下来的几篇的分析和讨论中,我们只对分析方法和研究过程做讨论,而不涉及任何有关漏洞的细节,这与此前的关于海康萤石智能网关的文章类似。
2.1 开锁流程分析
在之前的文章中,我们分析了果加智能门锁,链接为https://bbs.pediy.com/thread-259530.htm,在本次分析中我们依旧以BLE开锁为分析切入点。云丁鹿客的Loock Touch门锁同样可以通过手机BLE直接开锁,那么我们就从app的开锁流程开始分析。
应用市场中下载到的云丁鹿客智能门锁的配套app加了梆梆企业版的壳,脱壳之后可以看到该app打印出来的日志(脱壳不在本系列文章的讨论范畴中,分析iOS版也是可以的)。我们操作手机开启门锁后,app的日志中有这样几条记录吸引到了我们。图2-1 app开锁时打印的日志
根据上图中3个红框中的记录内容,我们可以推测开锁过程是基于Challenge/Response模式进行认证的,具体过程如下:
(1)app与门锁建立BLE连接后,当使用app开启门锁时,app会先下发一条通知指令,告知门锁准备进入开锁流程;
(2)随后门锁会向手机发送一组数字,称之为Challenge;
(3)app对Challenge进行处理生成Response并发送给门锁,门锁验证Response正确时,就会开锁。
我们对比了多次开锁时的app日志,发现每次开锁时第(1)步发送的通知指令仅有一个字节内容不同,如下图所示:
图2-2 通知指令对比
上图中,我们对比了3条通知指令,发现仅有第7个字节不同,在app封装数据包的代码段中,可以确定这个字节的含义是seqID,即数据包的序列号。因此我们可以推测这一条通知指令的作用类似于TCP协议中的SYN数据包,仅起到握手的作用,而门锁对手机的认证过程,主要是第(2)和第(3)步,那么接下来我们就分析一下app是如何处理Challenge并生成Response的。
2.2 Challenge的处理
2.2.1 Challenge处理流程分析
通过在代码中搜索日志内容,我们可以确定,app收到Challenge数据后,会由下图中的方法进行处理。上图中, handleCommand方法用于处理BLE返回数据,门锁返回的Challenge就作为参数传给了方法sendUnlockCommandNewProtocol,从参数和方法名称来看,这就是处理Challenge数据、生成Response并发送回门锁的方法。进一步查看代码,我们发现在sendUnlockCommandNewProtocol方法中,最终调用了下图中的encryptBleKeyNewProtocol方法对Challenge数据进行处理,该方法返回后的数据,添加包头后会被直接发送给门锁。
图2-4 对Challenge数据进行加密
上图中,参数arg13即为Challenge数据,可以看到该方法中使用了AES EBC加密,密钥是参数arg9,加密完成后使用密文又异或了一组固定数据。经过以上分析我们可以推测,开锁流程中,门锁对手机的认证依赖于图2-3中的AES EBC算法,即双方使用相同的密钥对Challenge进行加密和解密处理,仅当手机拥有正确的AES密钥才能通过认证开启门锁。那么开锁流程的安全性,就取决于门锁与手机使用的密钥是否安全了。
2.2.2 Challenge加密密钥的获取
图2-3中,加密密钥是参数arg9,通过对arg9的回溯可以确定,对Challenge进行加密的密钥通过resetBleToken方法获取,如下图所示。
resetBleToken方法中发出了一个http请求,收到返回的BleToken并进行处理后,会对一些变量进行赋值。图中的mBleKey是BleKeyInfo类型的变量,其token成员变量,就是2.2.1章节中Challenge数据的加密密钥。下文中,我们将以BleKey来代称这个加密密钥。
由以上分析可以知道,BleKey是app从服务器上请求到的,这里就有两种可能:(1)每次app与门锁建立通信时,都会请求一个Key并下发给门锁;
(2)当app与门锁建立通信时,如果没有可用的BleKey,才会向服务器发送请求。
为了验证以上推测,我们使用手机多次重复开启门锁,这样使手机和门锁反复建立通信,这时我们在app日志和抓取到的数据包中,都没有发现这个请求以及相关内容,可以判断app应该是本地保存了BleKey,而不需要从服务器获得了。
我们可以通过清空app缓存的方式,删除本地存储的BleKey,删除后app开锁时的日志发生了一些变化,如下图所示:
图2-6 删除app的蓝牙钥匙后app开启门锁时的日志
对比图2-6和图2-1中的日志内容,可以看到删除BleKey后,在开启门锁时先执行了resetBleToken,这和我们的分析吻合。这里我们注意到,resetBleToken之后,还有另外一条日志sendBleKeyCommandNewProtocol,这里是向门锁发送BleKey的方法,下面我们先继续手机获取BleKey这一过程的分析,手机向门锁下发BleKey的过程将在2.2.4节中分析。
与此同时,我们在fiddler中也抓到的对应的http请求,如下图所示:app收到返回的BleToken后,会使用AES CBC算法,对上图中红框内的secret和token进行解密,相关代码如下图所示:此时可以确定,app从服务器获取到的BleKey同样被AES CBC加密算法保存。通过图2-8可以看到,其密钥由方法getCryptSecret返回,与方法名称对应,这个密钥我们就称之为CryptSecret,下一步我们就来分析CryptSecret的来源。2.2.3 CryptSecret的获取
BleKey解密时的密钥通过getCryptSecret方法获得。那么我们可以从这个方法入手,相关代码的搜索结果如下图所示。上图中,getCryptSecret方法返回的是this.mCryptSecret,而这个变量是通过setCryptSecret赋值的,setCryptSecret方法则是在getSecret方法中被调用。在getSecret方法中,可以看到CryptSecret是从云丁鹿客的服务器获取到的,其url为”api/v1/crypt_secret”。同样的,我们在fiddler里也可以抓到crypt_secret数据包,如下图:
图2-10 crypt_secret请求及其响应
2.2.4向门锁下发BleKey
2.2.2节中说到sendBleKeyCommandNewProtocol函数负责向门锁下发BleKey,该函数如下图所示:结合上图与图2-6的日志和图2-7中reset_token请求的返回数据包,可以发现,手机向门锁下发BleKey的流程非常简单,就是对reset_token返回数据包中的totalData进行base64解码,解码后的二进制数据直接通过BLE通信发送给了门锁,门锁对totalData的处理我们将在后续文章中分析。2.3 app开锁流程小结
经过第二章的分析,我们可以将Loock Touch门锁的开锁流程总结如下图所示。
图2-12 开锁流程图上图中,开锁流程可以分为两个部分:
(1)虚线部分表示,当app没有存储可用的BleKey时,会向服务器请求CryptKey和BleToken两组数据,以CryptSecret为密钥,对BleToken中的数据进行AES CBC解密后,就可以得到BleKey。
(2)实线部分则是当app中有可用的BleKey时,会以BleKey为密钥对Challenge进行AES EBC加密,密文异或一组固定数据后,作为Response的主要内容并发送给门锁,当Response校验成功后才会开启门锁。
上文中,我们着重关注的是手机端是如何处理和发送BLE消息的,那么接下来就看看门锁这边针对BLE消息的处理流程。3.1 调试接口与调试器
在此前的文章中,我们已经介绍了通过gdbserver调试海康萤石固件程序的方法,链接为https://bbs.pediy.com/thread-261679.htm,通常情况下,gdbserver是运行于Linux系统环境中的调试器程序,通过使用Linux系统提供的调试接口以完成调试工作,并不需要太多硬件上的辅助。而在某些场景中,例如,没有Linux操作系统时,gdbserver等工具就无法满足我们的调试需求了,此时需要直接使用MCU提供的硬件调试接口和功能,以完成调试工作。通过阅读MCU的芯片手册,可以找到MCU提供的调试方式、调试引脚等详细信息。
为了使用MCU的调试接口,我们还需要与之对应的硬件调试器。与IDA、GDB等调试器软件类似,通过硬件调试器,我们也可以完全掌握被调试程序的执行状况。一般情况下,硬件调试器是通过USB接口连接到PC主机的,而硬件调试器与MCU之间的通信接口则有许多种。不同的MCU,支持的接口也不尽相同,具体信息需要查阅各芯片的手册。
本篇文章要研究的Loock Touch门锁使用的MCU型号为EFM32GG280F512,通过翻阅芯片手册,可以找到其调试接口信息如下图:
上图中的调试接口名称为SWD接口,常见的使用SWD接口的硬件调试器有JLink、STLink等。
我们目前使用的调试器SEGGER JLink(下文简称为JLink)提供了对JTAG和SWD两种调试接口的支持,具体使用时,只需要连接到对应的引脚即可,JLink中两种接口的引脚如下图所示:
图3-2 SEGGER JLink的引脚定义
3.2 通过调试接口提取固件
JLink调试器可以通过调试接口读写芯片内置的Flash,从而获取完整的固件。并不是所有芯片都可以通过调试接口提取固件,很多设备在发行版中启用了读保护(Readout Protection)机制,使得我们无法通过调试接口读到Flash内容,所幸本文中的设备并没有启用该机制。
接下来,我们就寻找并尝试用JLink连接芯片的调试接口。3.2.1 连接调试接口
在门锁的电路板上,主控MCU一旁有一排过孔,我们已经在将排针焊接到这排过孔上了,如下图所示:图中,左侧红框内就是我们焊上的排针,电路板上标注了排针对应的MCU引脚名称。经过万用表测量,可以确定红框中的IO和CLK引脚就是芯片的SWDIO和SWCLK两个引脚,我们在图2-1中提到这两个引脚是SWD调试接口的一部分,那么我们将这几个引脚按照如下图的方式连接到JLink调试器。上图左侧是图3-1中的排针,右侧是SEGGER JLink的引脚。连接完成后,将JLink连接到电脑的USB口,并打开JLink Commander命令行工具(JLink的相关工具和使用手册等都可以在SEGGER的官网下载到:https://www.segger.com/downloads/jlink/),执行下图中的命令:下面逐一解释上图中6个红框的内容:
(1)命令行工具开启时,会首先对JLink的状态进行检测(SEGGER官网的JLink,无论价格还是到货时间都不理想,所以我们选择了某宝,从这里打印的JLink固件信息来看,某宝的似乎是盗版产品);
(2)输入connect指令,控制JLink开始通过调试接口连接MCU;
(3)输入?指令选择要调试的芯片型号,在弹出的对话框中,我们按照芯片的厂商和型号,选择如下图的选项;
图3-6 芯片型号选择
(5)选择接口的通信速率,一般保持默认即可;
(6)当出现第6个红框中的内容时,说明JLink已经通过SWD接口连接到了MCU,接下来我们就可以读取固件或者进行调试了。
3.2.2 提取固件
上文提到,调试器可以读写芯片的内置Flash,而固件代码就存储在芯片内置Flash中,所以我们只需要将Flash中代码区域的数据读出并保存下来,就相当于拿到了固件。按照我们分析果加门锁时的思路,翻阅芯片手册,可以确定代码存储在内存地址为0x0~0x100000的区域。我们通过JLink Commander命令行工具的savebin命令(该命令的使用方法可以去JLink的手册中查阅)将这一区域中的二进制数据保存下来。上图中,我们拿到了固件代码,并将其命名为LoockEfm32Fw.bin。3.2.3 解析固件
在拿到固件之后,我们需要解析固件并对其进行逆向分析。通过查阅手册可以知道,EFM32GG280的核心是ARM Cortex-M3,和果加门锁用到的STM32L0同属Cortex-M系列,所以我们可以按照果加门锁相关篇章中的方法配置IDA并载入固件。载入之后,继续沿用果加门锁分析时的思路,寻找Reset中断的中断服务程序,即偏移0x4地址处的数据,跳转之后按“c”键,IDA就会自动开始解析代码,如下图所示:上文提到,代码存储在内存地址为0x0~0x100000的区域,所以图3-6中固件的基址为0x0。此前,我们在分析果加门锁时,将固件载入IDA时基址是0x08000000,因为在果加的案例中,代码存储区域的起始地址是0x08000000。至此,我们成功提取并解析了Loock Touch门锁的固件。3.3 通过调试接口调试固件
3.3.1 SEGGER JLink的调试功能
在进行调试之前,先简单介绍一下如何使用SEGGER JLink(下文以JLink代称)的调试功能。上一篇文章中,我们在提取固件时使用了JLink的命令行工具,这个命令行工具也包含了诸如设置断点、暂停CPU核心、读取/写入寄存器或指定位置的内存、单步调试等动态调试所需要的指令。具体可以查阅SEGGER的Wiki(https://wiki.segger.com/J-Link_Commander)。可以选择openOCD配合JLink进行调试,但我们在本篇中暂不介绍openOCD,而是选择另一款调试工具。
在实际调试过程中,命令行工具通常需要搭配IDA使用,用起来非常繁琐,且信息展示不够直观。SEGGER提供了一个图形界面工具——Ozone,可以在一定程度上解决以上问题,关于Ozone的介绍可以参考SEGGER的官方网站(https://www.segger.com/products/development-tools/ozone-j-link-debugger/),软件以及使用手册的下载页面也可以在这里找到。下载后的安装一路next就可以了,想必大家都很熟练。
安装完成后,可以通过“File->New->New Project Wizard”选项来创建新的项目,创建时需要选择待调试芯片的型号、调试接口类型、通信速率等信息。继续,点击“Debug->Start Debug Session->Attach to Running Program”连接到待调试设备,注意整个调试过程中需要保持电脑、调试器和设备的接通状态,如下图所示。连接到待调试设备后,Ozone可以像IDA一样展示多个subview,这样就可以同步观察很多信息了,下图是笔者某次调试的界面。
图3-10 Ozone调试界面
如上图所示,我们在调试时可以一次性看到寄存器的数据、多个内存区域的数据以及反汇编后的代码,下方的控制台区域可以执行一些指令或预先编写好的脚本。除此之外,左上角的断点设置区域,可以看到断点除了Location以外,还有Type和Extra两个属性,通过阅读用户手册可以确定这两个属性是用于设置断点类型的,如执行断点,读写断点、TRACE断点等,如下图所示:Ozone还提供了很多强大的功能,在后文中,我们用到的时候就会逐一介绍这些功能。3.3.2 通过SWD接口对门锁进行调试
翻阅门锁MCU的芯片手册可以看到,芯片提供了AES处理模块,该模块的内存映射如下图所示:显然,我们假设鹿客门锁使用芯片的AES模块进行加解密操作,而不是自写AES算法,那么必然需要访问0x400E0000~0x400E0400这片区域中的内存地址,那么我们只要在设置适当的读写断点,然后等待手机与门锁进行通信时触发断点就可以了。
在断点设置区域右键,选择Set Data Breakpoint,会弹出如下图所示的窗口:上图中的设置,表示当CPU向0x400E00XX地址写入数据时触发断点。
断点设置完成之后,在手机上点击开锁,由于我们设置的数据断点是监控一片内存区域,所以开锁过程中会多次被触发,后可以看到如下图左侧的一小段代码,右侧是执行到0xED0C地址时的寄存器数据。上图中,0x400E001C地址(R0 + 28)是AES_DATA寄存器,这一小段代码所处的函数向AES_DATA寄存器写数据,应该就是AES的处理函数(下文以AESFunc代称)。回溯调用栈,可以找到调用AESFunc的外层函数,以及AESFunc函数的起始地址,进而使用IDA的F5功能对AESFunc函数进行分析,如下图:结合芯片手册,由伪代码很容易能判断出AESFunc各个参数的作用,在本系列第一篇文章中,我们已经知道了开锁时的解密密钥就是BleKey,而门锁BleKey的获取过程,是由服务器下发一组数据totalData至手机app,app没有进行任何处理,直接通过BLE通信转发给了门锁。那么,接下来看一看门锁是如何处理totalData的。
首先我们需要看一下,totalData的内容是什么,如下图所示:
图3-16 totalData字段及其base64解码数据
可以看到totalData是一串二进制数据经过base64编码后的结果,红框之前的部分可以视作数据的header,包含消息头、数据包序号、校验等内容, header之后的body部分,即红、蓝、黑框中的数据,这三组数据的结构是相同的,如下图所示:
图3-17 totalData中payload用到的数据结构红框中data_type为0x03的数据,其data_content=0x5FFFECAE,该部分与AES根密钥密文有关。totalData的body部分还有两组数据,其具体作用不再详细说明,说太多有些不妥。
接着,我们在AESFunc的入口下一个断点,可以看到BleKey的值如下图所示:从图3-16和图3-18中看不到BleKey和totalData之间的联系,想必totalData和BleKey之间还是经过了某些解密或解码转换,我们需要继续寻找门锁对totalData的处理过程。保持AESFunc函数的断点,在调试过程中可以发现totalData也是经过 AESFunc函数进行解密的。此时,回溯调用栈即可找到如下图所示的关键代码。
图3-19 totalData数据的处理
上图中的AESEntry函数为AESFunc的封装,这部分就是由totalData生成BleKey的核心代码,该流程可以整理为下图。在这个流程中,如果没有根密钥就无法解密BleKey,通过调试可以找到根密钥在Flash中的存储地址是0x7E09C。在第三章中,我们直接用SWD调试器定位到了关键代码,这种分析方法固然好,但却跳过了很多有价值的地方。在第四章中,我们回过头来从另一个角度分析这个设备,即从设备上电开始的第一条代码开始分析,一点一点理解整个固件的内容。4.1 bootloader分析
bootloader是设备上电之后首先运行的一段程序,它会完成必要的初始化操作,并引导操作系统的加载。在我们拿到设备固件,并顺利通过IDA载入固件之后,可以看到解析出的内容仅是完整固件的一小部分,而更多的固件内容还是未解析状态。这被解析出来的一小部分,其实就是设备的bootloader部分代码。
为帮助我们分析这个云丁鹿客的智能门锁,我们可以尝试接通设备的串口。串口的位置就在SWD接口的旁边,可以参考云丁鹿客智能门锁系列第二篇中的图3-1。此时,给设备上电,是可以看到串口有字符串输出的,如下图所示:
上图中,前几条字符串是可以直接在IDA中搜索到引用的,而剩下的字符串则未找到引用地址,如下图:
图4-2 字符串引用
造成上图中现象的原因是,IDA仅仅解析了bootloader部分代码,这些字符串的引用代码刚好是bootloader部分输出的。其余字符串是由主程序输出的,IDA未能解析出主程序而导致这些字符串在代码中未被引用。上图中,程序最终进入sub_C44函数,如下图:
图4-4 sub_C44函数
上图函数的功能很简单,即将0x4100存储到向量表偏移寄存器(0xE000ED08地址),也就是将0x4100地址的数据设置为新的中断向量表,随后调用sub_F0函数。结合图4-3和图4-4,bootloader最后会把0x2001B860(0x4100地址处的值)设置为新的栈指针,并跳转到0x49ED(0x4104地址处的值)。从这之后bootloader就将MCU的控制权交给了主程序。
综上,设备上电后会首先执行bootloader,bootloader会在Flash中搜索、校验固件,校验完成后,会设置新的中断向量表并跳转至主程序的入口地址。4.2 FreeRTOS分析
通过固件中的字符串可以推测出设备使用了FreeRTOS操作系统,该系统是一种很常见的开源实时操作系统,其官方主页是:https://www.freertos.org。在使用FreeRTOS系统的设备中,固件主程序是FreeRTOS系统内核代码和应用逻辑代码相互杂糅在一起的一个二进制文件,所以我们需要先设法将二者区分开来,否则就容易出现山总分析MFC42一样的错误。
FreeRTOS操作系统提供了任务管理的功能,而设备的逻辑功能就是由其中一个或多个任务(task)来实现的,所以应用逻辑代码必然在这些task的实现代码中。FreeRTOS系统创建任务的API如下图所示:
图4-6 FreeRTOS任务创建API
上图中,函数的第一个参数是该任务对应的函数指针,第二个参数是任务的名称,只要找到这个API的调用处,通过它的第一个参数就可以定位任务函数了。
由于FreeRTOS是开源的操作,我们可以使用源码编译生成一个带符号表的程序,进而利用IDA的bindiff插件对比识别固件中的FreeRTOS操作系统API。此处的bindiff插件是一款IDA插件,用于比较两个idb (IDA database)文件中的相似函数,常见于漏洞的补丁分析,其官方网址是https://www.zynamics.com/bindiff.html。按照官网的安装说明,下载相应版本的bindiff安装程序,完成安装即可使用。
为了提高bindiff插件对比的准确率,我们需要尽量选择与云丁鹿客近似的FreeRTOS版本,编译环境和配置也尽量靠近云丁鹿客的智能门锁。有趣的是,在我们搜索可用的源码过程中,意外地发现github上有一份代码与我们正在逆向分析的固件极其相似。链接是https://github.com/RunningChild/efm32_freertos_app,后发现该仓库的拥有者就是云丁鹿客的工作人员,这份代码是把关键应用逻辑代码全部删掉之后剩下的FreeRTOS框架。
顺利编译该项目之后即可使用bindiff插件做比较,具体编译方法和过程会在以后的文章中专门做整理和介绍,本篇番外中暂不做过多介绍,部分对比结果如下图所示:
图4-7 bdiff部分结果
上图中我们编译的固件中xTaskGenericCreate函数和原版固件中sub_34A50函数相似度非常高,可以判断sub_34A50函数就是FreeRTOS中创建任务的API接口,那么我们在IDA中查找sub_34A50函数的引用,某一处引用如下图:
上图中,sub_34A50函数的参数和图4-6中xTaskCreate的参数是吻合的。还可以判断出位于0x1B0A9地址处的函数负责的应该是BLE消息的接收和处理。在0x1B0A8地址处下一个断点开始调试,当手机app里点击开门按钮后就会触发断点,可以沿此线索继续往后分析。
由此我们可以确定sub_34A50函数就是固件用来创建任务的API,接下来只需要写一个简单的IDApython脚本,即可将固件中的所有任务都整理出来,并使IDA以这些任务的函数地址创建函数,脚本如图:注意,脚本功能依靠sub_34A50函数的交叉引用来实现,存在一些未被IDA识别的引用点,因此可能会漏掉一些task。至此,我们就可以随意分析云丁鹿客智能门锁中我们感兴趣的功能点了。到这里,关于云丁鹿客智能门锁的分析也结束了。在这篇文章中,我们通过逆向手机apk了解门锁蓝牙功能的通信数据中的AES加密保护。然后分享了如何使用SWD接口提取固件并进一步通过对门锁固件的调试,来分析门锁获取BleKey的过程;最后,又着重分析了设备启动后的bootloader代码以及FreeRTOS操作系统。作为一款开源的实时操作系统,FreeRTOS着实拥有不少的用户量,虽然分析起来要比嵌入式Linux操作系统的程序要麻烦一些,但仍然建议各位读者对其有一点简单的认识。希望本篇文章能给各位读者带来一些收获,如若有什么想商量或者讨论的,可以随时联系我们胖猴微信:PwnMonkey。在后续文章中,我们还会继续分享其他的研究案例,敬请期待。
看雪ID:胡一米
https://bbs.pediy.com/user-home-613694.htm
*本文由看雪论坛 胡一米 原创,转载请注明来自看雪社区